package abel.wlmr.model.type;

import org.joda.time.DateTime;

import abel.util.StdBcdNumber;
import abel.wlmr.model.WlmrUserData;
import abel.wlmr.model.WlmrException;
import io.netty.buffer.ByteBuf;

/**
 * C.1 数据格式 01
 * 格式定义见表 C.1。
 * 表C.1 数据格式 01 定义
 * 字节名称
 * 字节格式
 * D7 D6 D5 D4 D3 D2 D1 D0
 * 秒 BCD 码十位 BCD 码个位
 * 分 BCD 码十位 BCD 码个位
 * 时 BCD 码十位 BCD 码个位
 * 日 BCD 码十位 BCD 码个位
 * 星期-月 星期
 * BCD 码个位
 * 月
 * BCD 码十位
 * 月
 * BCD 码个位
 * 年 BCD 码十位 BCD 码个位
 * ——星期定义如下： D5～D7 编码表示 0～7， 0：无效， 1～7 依次表示星期一至星期日
 * 
 * @author Abel
 *
 */
public class C1DateTimeType extends WlmrUserData {

    public static final short FIXED_LEN = 4 + 1 + 1;

    private StdBcdNumber secDay;

    private short weekMonth;

    private StdBcdNumber year;

    private DateTime dtValue;

    public C1DateTimeType() {
        super();
    }

    public C1DateTimeType(short guessedLen) {
        super(guessedLen);
    }

    public C1DateTimeType(DateTime dt, boolean fillWeek) {
        fillJodaValue(dt, fillWeek);
    }

    @Override
    public int getMinLength() {
        return FIXED_LEN;
    }

    @Override
    public short getFinalLen() {
        return FIXED_LEN;
    }

    @Override
    public ByteBuf appendBuf(ByteBuf buf) {
        if (dtValue == null) {
            throw new IllegalArgumentException("clock can't be null");
        }

        buf.writeBytes(secDay.toBytes());
        buf.writeByte(weekMonth);
        buf.writeBytes(year.toBytes());

        return buf;
    }

    @Override
    public int parseBuf(ByteBuf buf) throws WlmrException {
        if (checkBuffer(buf) < 0) {
            return -1;
        }

        secDay = new StdBcdNumber();
        secDay.populateBytes(buf, 4);

        weekMonth = (short) (buf.readByte() & 0xFF);

        year = new StdBcdNumber();
        year.populateBytes(buf, 1);

        try {
            dtValue = convertToJodaValue(false);
        } catch (IllegalArgumentException e) {
            throw new WlmrException("invalid C.1 date time", e);
        }

        return getFinalLen();
    }

    /**
     * Fill this object with joda {@link DateTime} object. Week may not be filled
     * if <code>flllWeek</code> is false.
     * 
     * @param dt
     * @param fillWeek
     */
    public C1DateTimeType fillJodaValue(DateTime dt, boolean fillWeek) {
        this.dtValue = dt;

        secDay = new StdBcdNumber();
        secDay.populateDecimal(new byte[] {
            (byte) dt.getSecondOfMinute(),
            (byte) dt.getMinuteOfHour(),
            (byte) dt.getHourOfDay(),
            (byte) dt.getDayOfMonth()
        });

        weekMonth = fillWeek
            ? (short) (dt.getDayOfWeek() << 5 | (dt.getMonthOfYear() / 10) << 4 | (dt.getMonthOfYear() % 10))
            : (short) ((dt.getMonthOfYear() / 10) << 4 | (dt.getMonthOfYear() % 10));

        year = new StdBcdNumber();
        year.populateDecimal(new byte[] {
            (byte) (dt.getYear() % 100)
        });

        return this;
    }

    /**
     * Converts to joda {@link DateTime} from this object. Throws IAE if there's any error.
     * Do not check week bits if checkWeek is false.
     * 
     * @param checkWeek
     * @return
     */
    private DateTime convertToJodaValue(boolean checkWeek) {
        if (secDay == null || secDay.length() != 4) {
            throw new IllegalArgumentException("second to day can't be null");
        }

        byte week = (byte) ((weekMonth >> 5) & 0x07);
        byte mth = (byte) (((weekMonth >> 4) & 0x01) * 10 + weekMonth & 0x0F);
        if (mth < 1 || mth > 12) {
            throw new IllegalArgumentException("week_month is invalid");
        }

        if (year == null || year.length() != 1) {
            throw new IllegalArgumentException("invalid year");
        }

        byte[] decs = secDay.toDecimal();
        DateTime dt = new DateTime(DateTime.now().getYear() / 100 * 100 + year.toDecimal()[0],
            mth, decs[3], decs[2], decs[1], decs[0]);

        if (checkWeek && dt.getDayOfWeek() != week) {
            throw new IllegalArgumentException("invalid week");
        }

        return dt;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return String.format("{secDay:%s, weekMonth:%s, year:%s, joda:%s}", secDay, weekMonth, year, dtValue);
    }

    /**
     * @return the dtValue
     */
    public DateTime getDtValue() {
        return dtValue;
    }

}
